package pkg

import (
	"encoding/json"
	"fmt"
	"gitee.com/wayjin/fabric-extension/cryptoutil"
	"gitee.com/wayjin/fabric-extension/cryptoutil/caclient"
	"gitee.com/wayjin/fabric-extension/gateway"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
)

var userCryptoPath = "peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp"

var user1 = "/home/zuvakin/workspace/src/gitee.com/wayjin/fabric-explorer/temp/tom"

type CryptoOption struct {
	HashFamily   string `json:"hashFamily"`
	KeyStorePath string `json:"keystorePath"`
	BasePath     string `json:"basePath"`
}

type peerConfig struct {
	Host        string
	ServiceName string
}

type caSpec struct {
	Host string  `json:"host"`
	TLS  keyPair `json:"tls"`
}

type keyPair struct {
	Cert string `json:"cert"`
	Key  string `json:"key"`
}

type PeerOrgConfig struct {
	MSPID       string `json:"mspid"`
	TLSRootCert string `json:"tlsrootCert"`
	Peers       map[string]peerConfig
	Admin       keyPair `json:"admin"`
	CASpec      caSpec  `json:"caSpec"`
}

type OrdererOrgConfig struct {
	MSPID       string                `json:"mspid"`
	TLSRootCert string                `json:"tlsrootCert"`
	Orderers    map[string]peerConfig `json:"orderers"`
}

type Config struct {
	CryptoOpt  CryptoOption             `json:"cryptoOpt"`
	PeerOrgs   map[string]PeerOrgConfig `json:"peerOrgs"`
	OrdererOrg OrdererOrgConfig         `json:"ordererOrg"`
}

type Service struct {
	config Config
}

// For test
var userCtx = gateway.UserContext{}

func NewService(configFile string) (*Service, error) {
	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}
	var config Config
	err = json.Unmarshal(data, &config)
	if err != nil {
		return nil, err
	}

	//userCtx.CryptoPath = filepath.Join(config.CryptoOpt.BasePath, userCryptoPath)
	userCtx.CryptoPath = user1
	userCtx.MspID = "Org1MSP"

	return &Service{config: config}, nil
}
func (svc Service) getPeerContext(orgName, peerID string) (ctx gateway.PeerContext, err error) {
	orgConfig, ok := svc.config.PeerOrgs[orgName]
	if !ok {
		err = fmt.Errorf("not found config of organization[%s]", orgName)
		return
	}
	peerConfig, ok := orgConfig.Peers[peerID]
	if !ok {
		err = fmt.Errorf("not found config of peer[%s] in organization[%s]", peerID, orgName)
		return
	}
	ctx = gateway.PeerContext{Host: peerConfig.Host, ServiceName: peerConfig.ServiceName,
		RootTLSCert: filepath.Join(svc.config.CryptoOpt.BasePath, orgConfig.TLSRootCert)}
	return
}

func (svc Service) getOrdererContext(ordererID string) (ctx gateway.OrdererContext, err error) {
	ordererConfig, ok := svc.config.OrdererOrg.Orderers[ordererID]
	if !ok {
		err = fmt.Errorf("not found config of orderer [%s]", ordererID)
		return
	}
	ctx = gateway.OrdererContext{
		Host:        ordererConfig.Host,
		ServiceName: ordererConfig.ServiceName,
		RootTLSCert: filepath.Join(svc.config.CryptoOpt.BasePath, svc.config.OrdererOrg.TLSRootCert),
	}
	return
}

func (svc Service) getDefaultOrderer() (ctx gateway.OrdererContext, err error) {
	for _, config := range svc.config.OrdererOrg.Orderers {
		ctx = gateway.OrdererContext{
			Host:        config.Host,
			ServiceName: config.ServiceName,
			RootTLSCert: filepath.Join(svc.config.CryptoOpt.BasePath, svc.config.OrdererOrg.TLSRootCert),
		}
		return
	}
	err = fmt.Errorf("not found valid orderer config for service")
	return
}

func (svc Service) getCryptoSuite(mspid, username string, pwd []byte) (cryptoutil.CryptoSuite, error) {
	return cryptoutil.GetMyCryptoSuiteFromPath(filepath.Join(svc.config.CryptoOpt.KeyStorePath, username), mspid, pwd)
}

func (svc Service) getMSPID(orgName string) (string, error) {
	org, ok := svc.config.PeerOrgs[orgName]
	if !ok {
		return "", fmt.Errorf("not found org[%s] config", orgName)
	}
	return org.MSPID, nil
}

type ChannelRequest struct {
	OrgName  string `form:"orgName" binding:"required"`
	PeerID   string `form:"peer" binding:"required"`
	Username string `form:"username" binding:"required"`
	UserPwd  string `form:"secret"`
}

// rest api definition
func (svc Service) ListChannels(ctx *gin.Context) {
	var req ChannelRequest
	err := ctx.ShouldBind(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error(), "message": "parse request fail"})
		return
	}
	peerCtx, err := svc.getPeerContext(req.OrgName, req.PeerID)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	mspid, err := svc.getMSPID(req.OrgName)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	suite, err := svc.getCryptoSuite(mspid, req.Username, []byte(req.UserPwd))
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	channels, err := gateway.ListChannels(suite, peerCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, gin.H{"channels": channels})
}

func (svc Service) GetBlockChain(ctx *gin.Context) {
	channelID := ctx.Param("channelid")
	if channelID == "" {
		ctx.JSON(400, gin.H{"error": "channel id is required"})
		return
	}
	var req ChannelRequest
	err := ctx.ShouldBind(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	peerCtx, err := svc.getPeerContext(req.OrgName, req.PeerID)
	if err != nil {
		return
	}
	mspid, err := svc.getMSPID(req.OrgName)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	suite, err := svc.getCryptoSuite(mspid, req.Username, []byte(req.UserPwd))
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	blockChain, err := gateway.GetChannelInfo(suite, channelID, peerCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, gin.H{"blockchain": blockChain})
}

// GetBlock: get block from fabric network by number
func (svc Service) GetBlock(ctx *gin.Context) {
	number := ctx.Param("number")
	blockNum, err := strconv.ParseInt(number, 10, 64)
	if err != nil {
		ctx.JSON(400, err)
		return
	}
	fmt.Println("blockNumber:", blockNum)
	channelid := ctx.Param("channelid")
	if channelid == "" {
		ctx.JSON(400, gin.H{"error": "channel id is required"})
		return
	}
	ordererCtx, err := svc.getDefaultOrderer()
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	var req ChannelRequest
	err = ctx.ShouldBind(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	mspid, err := svc.getMSPID(req.OrgName)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	suite, err := svc.getCryptoSuite(mspid, req.Username, []byte(req.UserPwd))
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	block, err := gateway.GetBlock(suite, channelid, ordererCtx, uint64(blockNum))
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, block)
}

// ListBlocks: list top count blocks of hyperledger fabric
func (svc Service) ListBlocks(ctx *gin.Context) {
	channelid := ctx.Param("channelid")
	if channelid == "" {
		ctx.JSON(400, gin.H{"error": "channel id is required"})
		return
	}
	count := ctx.Param("count")
	topCount, err := strconv.ParseInt(count, 10, 64)
	if err != nil {
		ctx.JSON(400, err)
		return
	}

	var req ChannelRequest
	err = ctx.ShouldBind(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	mspid, err := svc.getMSPID(req.OrgName)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	suite, err := svc.getCryptoSuite(mspid, req.Username, []byte(req.UserPwd))
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	ordererCtx, _ := svc.getDefaultOrderer()
	blocks, err := gateway.GetTopCountBlocks(suite, channelid, uint64(topCount), ordererCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, gin.H{"blocks": blocks})
}

// GetChaincode: get chaincode info, not ready
func (svc Service) GetChainCode(ctx *gin.Context) {
	ctx.JSON(200, gin.H{"message": "not ready"})
}

// ListChainCode: list chaincodes of peer that has been installed
func (svc Service) ListChainCode(ctx *gin.Context) {
	peerCtx, _ := svc.getPeerContext("org1", "peer0")
	channelID := ctx.Param("channelid")
	if channelID == "" {
		ctx.JSON(400, gin.H{"error": "channel id is required"})
		return
	}

	suite, err := userCtx.CryptoSuite()
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	chaincodes, err := gateway.ListChainCodes(suite, channelID, false, peerCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, gin.H{"listChaincode": chaincodes})
}

// QueryChainCode: chaincode query action
func (svc Service) QueryChainCode(ctx *gin.Context) {
	channelID := ctx.Param("channelid")
	if channelID == "" {
		ctx.JSON(400, gin.H{"error": "channel id is required"})
		return
	}
	ccname := ctx.Param("name")
	if ccname == "" {
		ctx.JSON(400, gin.H{"error": "chaincode name is empty"})
		return
	}
	ccversion := ctx.Param("version")
	if ccversion == "" {
		ctx.JSON(400, gin.H{"error": "chaincode version is empty"})
		return
	}

	req := ChainCodeRequest{}
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error":err.Error()})
		return
	}
	spec := gateway.ChaincodeSpec{
		Name: ccname,
		Args: req.Args,
	}

	odererCtx, _ := svc.getDefaultOrderer()
	peerCtx, _ := svc.getPeerContext(req.OrgName, req.PeerID)
	org, _ := svc.config.PeerOrgs[req.OrgName]

	suite, err := svc.getCryptoSuite(org.MSPID, req.Username, []byte(req.UserPwd)) //userCtx.CryptoSuite()
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error(), "user":req.Username})
		return
	}

	resp, err := gateway.QueryChainCode(suite, channelID, spec, odererCtx, peerCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	ctx.JSON(200, gin.H{"result": resp})
}

type ChainCodeRequest struct {
	OrgName  string `json:"orgName" binding:"required"`
	PeerID   string `json:"peer" binding:"required"`
	Args     string `json:"args" binding:"required"`
	Username string `json:"username" binding:"required"`
	UserPwd  string `json:"secret" binding:"required"`
}

// InvokeChainCode: chaincode invoke aciton
func (svc Service) InvokeChainCode(ctx *gin.Context) {
	channelID := ctx.Param("channelid")
	if channelID == "" {
		ctx.JSON(400, gin.H{"error": "channelid is required"})
		return
	}
	ccname := ctx.Param("name")
	if ccname == "" {
		ctx.JSON(400, gin.H{"error": "chaincode name is empty"})
		return
	}
	ccversion := ctx.Param("version")
	if ccversion == "" {
		ctx.JSON(400, gin.H{"error": "chaincode version is empty"})
		return
	}

	req := ChainCodeRequest{}
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctx.JSON(500, gin.H{"error":err.Error()})
		return
	}
	//args := ctx.PostForm("args")
	//if args == "" {
	//	ctx.JSON(400, gin.H{"error": "args not found"})
	//	return
	//}

	fmt.Println("args:", req.Args)
	spec := gateway.ChaincodeSpec{
		Name: ccname,
		//Version:       ccversion,
		//Lang:          "golang",
		Args: req.Args,
		WaitForEvent: true,
	}
	org, _ := svc.config.PeerOrgs[req.OrgName]
	suite, err := svc.getCryptoSuite(org.MSPID, req.Username, []byte(req.UserPwd)) //userCtx.CryptoSuite()
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	odererCtx, _ := svc.getDefaultOrderer()
	peerCtx, _ := svc.getPeerContext(req.OrgName, req.PeerID)
	err = gateway.InvokeChainCode(suite, channelID, spec, odererCtx, peerCtx)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(200, gin.H{"invoke": "success"})
}

type UserRegInfo struct {
	Name   string `json:"name"`
	Secret string `json:"secret"`
	OrgID  string `json:"orgid"`
	Attr   string
}

// RegisterUser: user register for query access rest api
func (svc Service) RegisterUser(ctx *gin.Context) {
	userInfo := UserRegInfo{}
	err := ctx.ShouldBindJSON(&userInfo)
	if err != nil {
		ctx.JSON(400, gin.H{"error": err.Error()})
		return
	}
	org, ok := svc.config.PeerOrgs[userInfo.OrgID]
	if !ok {
		ctx.JSON(500, gin.H{"error": "not found peer orgs"})
		return
	}
	client, err := caclient.NewClient2(org.CASpec.Host)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}
	fmt.Println(filepath.Join(svc.config.CryptoOpt.BasePath, org.Admin.Cert))
	_, err = cryptoutil.LoadKeyStoreFromFile(filepath.Join(svc.config.CryptoOpt.BasePath, org.Admin.Cert),
		filepath.Join(svc.config.CryptoOpt.BasePath, org.Admin.Key), nil)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	userReq := caclient.NewRegisterRequest(userInfo.Name, userInfo.Secret, "org1.example.com", caclient.ID_USER)
	userKs, err := client.RegisterAndEnroll(userReq, nil)
	if err != nil {
		ctx.JSON(500, gin.H{"error": err.Error()})
		return
	}

	prk := cryptoutil.NewPrivateKey(userKs.GetPrivateKey())
	signcertDir := filepath.Join(svc.config.CryptoOpt.KeyStorePath, userInfo.Name, "signcerts")
	keycertDir := filepath.Join(svc.config.CryptoOpt.KeyStorePath, userInfo.Name, "keystore")
	keyBytes, _ := prk.GetBytes([]byte(userInfo.Secret))
	err = os.MkdirAll(signcertDir, 0755)
	//fmt.Println("mkdir ", signcertDir, err)
	err = os.MkdirAll(keycertDir, 0755)
	//fmt.Println("mkdir", keycertDir, err)
	ioutil.WriteFile(filepath.Join(signcertDir, "cert.pem"), userKs.GetSignCert(), 0644)
	ioutil.WriteFile(filepath.Join(keycertDir, prk.FileName()), keyBytes, 0644)
}
